/*
 * Professional Android, 4th Edition
 * Reto Meier and Ian Lake
 * Copyright 2018 John Wiley Wiley & Sons, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.professionalandroid.apps.earthquake;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

//要求在设备上运行 Google Paly服务
import com.firebase.jobdispatcher.Constraint;
import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.Lifetime;
import com.firebase.jobdispatcher.SimpleJobService;
import com.firebase.jobdispatcher.Trigger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

public class EarthquakeUpdateJobService extends SimpleJobService {
  private static final String TAG = "EarthquakeUpdateJob ";
  /**
   * 一次性作业
   */
  private static final String UPDATE_JOB_TAG = "update_job";
  /**
   * 定期作业
   */
  private static final String PERIODIC_JOB_TAG = "periodic_job";


  /**
   * 用于构造每个通知（NOTIFICATION）的常量
   */
  private static final String NOTIFICATION_CHANNEL = "earthquake_ch11";
  public static final int NOTIFICATION_ID = 1001;

  public static void scheduleUpdateJob(Context context) {
    FirebaseJobDispatcher jobDispatcher =
      new FirebaseJobDispatcher(new GooglePlayDriver(context));

    jobDispatcher.schedule(jobDispatcher.newJobBuilder()
                             .setTag(UPDATE_JOB_TAG)
                             .setService(EarthquakeUpdateJobService.class)
                             .setConstraints(Constraint.ON_ANY_NETWORK)
            .setTrigger(Trigger.executionWindow(0,1))//立即触发//my code
                             .build());
  }

  @Override
  public int onRunJob(final JobParameters job) {
    Toast.makeText(this,"正在执行firebase JobService任务"+job.getTag() ,Toast.LENGTH_LONG).show();
    // Result ArrayList of parsed earthquakes.
    ArrayList<Earthquake> earthquakes = new ArrayList<>();

    // Get the XML
    URL url;
    try {
      String quakeFeed = getApplication().getString(R.string.earthquake_feed);

      url = new URL(quakeFeed);

      URLConnection connection;
      connection = url.openConnection();
      HttpURLConnection httpConnection = (HttpURLConnection) connection;

      int responseCode = httpConnection.getResponseCode();
      if (responseCode == HttpURLConnection.HTTP_OK) {
        InputStream in = httpConnection.getInputStream();
        DocumentBuilderFactory dbf =
          DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();

        // Parse the earthquake feed.
        Document dom = db.parse(in);
        Element docEle = dom.getDocumentElement();

        // Get a list of each earthquake entry.
        NodeList nl = docEle.getElementsByTagName("entry");
        if (nl != null && nl.getLength() > 0) {
          for (int i = 0; i < nl.getLength(); i++) {
            Element entry =
              (Element) nl.item(i);
            Element id =
              (Element) entry.getElementsByTagName("id").item(0);
            Element title =
              (Element) entry.getElementsByTagName("title").item(0);
            Element g =
              (Element) entry.getElementsByTagName("georss:point")
                          .item(0);
            Element when =
              (Element) entry.getElementsByTagName("updated").item(0);
            Element link =
              (Element) entry.getElementsByTagName("link").item(0);

            String idString = id.getFirstChild().getNodeValue();
            String details = title.getFirstChild().getNodeValue();
            String hostname = "";//http://earthquake.usgs.gov";
            String linkString = hostname + link.getAttribute("href");
            String point = g.getFirstChild().getNodeValue();
            String dt = when.getFirstChild().getNodeValue();

            SimpleDateFormat sdf =
              new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'Z'");
            Date qdate = new GregorianCalendar(0, 0, 0).getTime();

            try {
              qdate = sdf.parse(dt);
            } catch (ParseException e) {
              Log.e(TAG, "Date parsing exception.", e);
            }

            String[] location = point.split(" ");
            Location l = new Location("dummyGPS");
            l.setLatitude(Double.parseDouble(location[0]));
            l.setLongitude(Double.parseDouble(location[1]));

            String magnitudeString = details.split(" ")[1];
            int end = magnitudeString.length() - 1;
            double magnitude =
              Double.parseDouble(magnitudeString.substring(0, end));

            if (details.contains("-"))
              details = details.split("-")[1].trim();
            else
              details = "";

            final Earthquake earthquake = new Earthquake(idString,
              qdate,
              details, l,
              magnitude,
              linkString);

            // Add the new earthquake to our result array.
            earthquakes.add(earthquake);
          }
        }
      }
      httpConnection.disconnect();

//在定期作业中检查通知触发条件
      if (job.getTag().equals(PERIODIC_JOB_TAG)) {
        Earthquake largestNewEarthquake = findLargestNewEarthquake(earthquakes);

        SharedPreferences prefs =PreferenceManager.getDefaultSharedPreferences(this);

        int minimumMagnitude = Integer.parseInt(prefs.getString(PreferencesActivity.PREF_MIN_MAG, "3"));

        if (largestNewEarthquake != null && largestNewEarthquake.getMagnitude() >= minimumMagnitude) {
          // 触发一个通知 Trigger a Notification
          broadcastNotification(largestNewEarthquake);
        }
      }

      //todo测试查看通知样式  broadcastNotification(earthquakes.get(0));

      // Insert the newly parsed array of Earthquakes
      EarthquakeDatabaseAccessor
        .getInstance(getApplication())
        .earthquakeDAO()
        .insertEarthquakes(earthquakes);

      scheduleNextUpdate(this, job);

      return RESULT_SUCCESS;
    } catch (MalformedURLException e) {
      Log.e(TAG, "Malformed URL Exception", e);
      return RESULT_FAIL_NORETRY;
    } catch (IOException e) {
      Log.e(TAG, "IO Exception", e);
      return RESULT_FAIL_RETRY;
    } catch (ParserConfigurationException e) {
      Log.e(TAG, "Parser Configuration Exception", e);
      return RESULT_FAIL_NORETRY;
    } catch (SAXException e) {
      Log.e(TAG, "SAX Exception", e);
      return RESULT_FAIL_NORETRY;
    }
  }

  private void scheduleNextUpdate(Context context, JobParameters job) {
    if (job.getTag().equals(UPDATE_JOB_TAG)) {
      SharedPreferences prefs =
        PreferenceManager.getDefaultSharedPreferences(this);
//更新频率
      int updateFreq = Integer.parseInt(
        prefs.getString(PreferencesActivity.PREF_UPDATE_FREQ, "60"));
//是否自动更新
      boolean autoUpdateChecked =
        prefs.getBoolean(PreferencesActivity.PREF_AUTO_UPDATE, false);

      if (autoUpdateChecked) {
        FirebaseJobDispatcher jobDispatcher =
          new FirebaseJobDispatcher(new GooglePlayDriver(context));

        jobDispatcher.schedule(jobDispatcher.newJobBuilder()
                                 .setTag(PERIODIC_JOB_TAG)
                                 .setService(EarthquakeUpdateJobService.class)
                                 .setConstraints(Constraint.ON_ANY_NETWORK)
                                 .setReplaceCurrent(true)
                                 .setRecurring(true)
                                 .setTrigger(Trigger.executionWindow(
                                   updateFreq*60 / 2,
                                   updateFreq*60))
                                 .setLifetime(Lifetime.FOREVER)
                                 .build());
      }
    }
  }

  /**
   * 定义具有振动和灯光的高重要性通知，作为新地震需要高优先级
   */
  private void createNotificationChannel() {
    //仅在运行android8.0，26或更高版本之上的操作系统的设备上创建通知渠道，
    // 在此版本及之上版本也无需单独提供设置通知偏好的界面，应该将用户重定向到系统通知设置页面P274
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      CharSequence name = getString(R.string.earthquake_channel_name);
      NotificationChannel channel = new NotificationChannel(
              NOTIFICATION_CHANNEL,
              name,
              NotificationManager.IMPORTANCE_HIGH);
      channel.enableVibration(true);//使用默认的振动模式
      channel.enableLights(true);//使用灯光
      NotificationManager notificationManager =getSystemService(NotificationManager.class);
      notificationManager.createNotificationChannel(channel);
    }
  }

  /**
   * 广播通知
   * @param earthquake 地震数据
   */
  private void broadcastNotification(Earthquake earthquake) {

    createNotificationChannel();//创建通知渠道

    Intent startActivityIntent = new Intent(this, EarthquakeMainActivity.class);
    PendingIntent launchIntent = PendingIntent.getActivity(this, 0,
            startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);//标志表明如果描述的 PendingIntent 已经存在，则保留它，但用这个新 Intent 中的内容替换它的额外数据。

    final NotificationCompat.Builder earthquakeNotificationBuilder
            = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL);

    earthquakeNotificationBuilder
            .setSmallIcon(R.drawable.notification_icon)
            .setColor(ContextCompat.getColor(this, R.color.colorPrimary))
            .setDefaults(NotificationCompat.DEFAULT_ALL)//使用所有灯光振动声音的默认值
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setContentIntent(launchIntent)//p276。添加内容用于启动Activity
            .setAutoCancel(true)//在单击通知时关闭通知。也可以设置setDeleteIntent处理用户清除通知
            .setShowWhen(true);

    earthquakeNotificationBuilder
            .setWhen(earthquake.getDate().getTime())
            .setContentTitle("震级:" + earthquake.getMagnitude())
            .setContentText("地震详情"+earthquake.getDetails())
            .setStyle(new NotificationCompat.BigTextStyle()
                    .bigText("地震详情："+ earthquake.getDetails()));//使用扩展的通知样式。API级别16以上支持

    NotificationManagerCompat notificationManager= NotificationManagerCompat.from(this);
//    NotificationManager notificationManager = getSystemService(NotificationManager.class);
    notificationManager.notify(NOTIFICATION_ID,earthquakeNotificationBuilder.build());
  }

  /**
   * 比较两个地震列表，获取需要通知的地震数据，新数据
   * @param newEarthquakes 从网上获取到的新的地震数据，在添加到数据库之前发送通知
   * @return
   */
  private Earthquake findLargestNewEarthquake(List<Earthquake> newEarthquakes) {
    List<Earthquake> earthquakes = EarthquakeDatabaseAccessor
            .getInstance(getApplicationContext())
            .earthquakeDAO()
            .loadAllEarthquakesBlocking();//loadAllEarthquakes

    Earthquake largestNewEarthquake = null;

    for (Earthquake earthquake : newEarthquakes) {
      if (earthquakes.contains(earthquake)) {
        continue;
      }

      if (largestNewEarthquake == null   || earthquake.getMagnitude() >= largestNewEarthquake.getMagnitude()) {
        largestNewEarthquake = earthquake;
        break;
      }
    }
    return largestNewEarthquake;
  }
}